package com.example.chess.widgets;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import androidx.annotation.Nullable;

import com.example.chess.bean.BlackChess;
import com.example.chess.bean.Chess;
import com.example.chess.bean.WhiteChess;
import com.example.chess.utils.LogUtils;
import com.example.chess.utils.SettingSp;
import com.example.chess.utils.SoundEngine;
import com.example.chess.utils.Utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 棋盘视图
 * Created by xuyougen.
 */
public class ChessBoardView extends View {

    //棋盘的 行数、列数
    private static final int ROW_COUNT = 15, COLUMN_COUNT = 15;

    //棋盘相对 View 四边的边距
    private float offsetLeft;
    private float offsetTop;
    private float offsetRight;
    private float offsetBottom;

    //棋盘 横向线条 长度
    private float gridHorizontalLength;
    //棋盘 纵向线条 长度
    private float gridVerticalLength;
    //棋盘 横向线条 间距
    private float gridHorizontalSpacing;
    //棋盘 横向线条 间距
    private float gridVerticalSpacing;

    //棋盘缓存是否过期，true 为过期(View大小发生改变的时候)，需要重新绘制棋盘
    private boolean isBoardCacheExpired;
    //棋盘缓存位图，提高 View绘制速度
    private Bitmap boardCache;
    //棋盘画布
    private Canvas boardCanvas;

    //当前 View 画笔
    private Paint paint;

    //棋子的半径大小
    private float chessRadius;

    //棋子的历史记录
    private List<Chess> historySteps;

    //棋盘中所有的棋子，未落棋的位置为 null
    private Chess[][] chessArray;

    //当前触摸的行列位置，无触摸则为 null
    private int[] touchedRowCol = null;

    //上一次下的棋的类型, 默认白棋(让黑棋先行)
    private Chess.Type lastChessType = Chess.Type.WHITE;

    //赢家，没有则为 null
    private Chess.Type winner;

    //胜利之后 Callback
    private WinListener winListener;

    //机器人（白棋）
    private Robot robot;
    //true 为机器人模式（人机博弈）
    private boolean isRobotMode;

    private SoundEngine soundEngine;

    public ChessBoardView(Context context) {
        this(context, null);
    }

    public ChessBoardView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ChessBoardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize();
    }

    /**
     * 初始化基本参数
     */
    private void initialize() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        chessArray = new Chess[ROW_COUNT][COLUMN_COUNT];
        historySteps = new ArrayList<>();
        boardCanvas = new Canvas();
        robot = new Robot();
        isRobotMode = SettingSp.isRobotMode();
        soundEngine = SoundEngine.create();
        soundEngine.prepareResource();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        offsetLeft = getPaddingLeft();
        offsetTop = getPaddingTop();
        offsetRight = getPaddingRight();
        offsetBottom = getPaddingBottom();

        gridHorizontalLength = (w - offsetLeft - offsetRight);
        gridHorizontalSpacing = gridHorizontalLength / (COLUMN_COUNT - 1);
        gridVerticalLength = (h - offsetTop - offsetBottom);
        gridVerticalSpacing = gridVerticalLength / (ROW_COUNT - 1);

        chessRadius = gridHorizontalSpacing / 3f * 2f / 2f;

        //View 大小发生改变，标记棋盘缓存失效，重新生成
        isBoardCacheExpired = true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //使得棋盘的高和宽一样长
        heightMeasureSpec = widthMeasureSpec;
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        drawChessboard(canvas);
        drawChess(canvas);
        drawTouchEffect(canvas);
    }

    /**
     * 绘制棋盘
     *
     * @param canvas 绘制棋盘的画布
     */
    private void drawChessboard(Canvas canvas) {
        //检查缓存 是否存在、过期
        if (!isBoardCacheExpired && boardCache != null) {
            //从缓存中绘制
            drawChessboardFromCache(canvas);
            return;
        }

        //如果之前的缓存存在，清空位图
        if (boardCache != null)
            boardCache.recycle();
        //创建新的位图缓存
        boardCache = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        boardCanvas.setBitmap(boardCache);

        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(1);
        paint.setColor(Color.BLACK);

        boardCanvas.save();
        boardCanvas.translate(offsetLeft, offsetTop);
        for (int row = 0; row <= ROW_COUNT; row++) {
            boardCanvas.save();
            boardCanvas.translate(0, row * gridVerticalSpacing);
            //绘制行线
            boardCanvas.drawLine(0, 0, gridHorizontalLength, 0, paint);
            boardCanvas.restore();
            for (int col = 0; col <= COLUMN_COUNT; col++) {
                boardCanvas.save();
                boardCanvas.translate(col * gridHorizontalSpacing, 0);
                //绘制列线
                boardCanvas.drawLine(0, 0, 0, gridVerticalLength, paint);
                boardCanvas.restore();
            }
        }
        float outerBorderWidth = Utils.dp2px(2f);
        float outerBorderOffset = outerBorderWidth * 2f;
        paint.setStrokeWidth(outerBorderWidth);
        boardCanvas.drawRect(-outerBorderOffset, -outerBorderOffset,
                gridHorizontalLength + outerBorderOffset, gridVerticalLength + outerBorderOffset, paint);
        boardCanvas.restore();

        isBoardCacheExpired = false;
        LogUtils.e("棋盘缓存生成完毕");

        //缓存生成完毕，绘制棋盘到 View中
        drawChessboardFromCache(canvas);
    }

    /**
     * 从缓存中绘制棋盘
     *
     * @param canvas 需要被绘制的画布
     */
    private void drawChessboardFromCache(Canvas canvas) {
        if (boardCache == null)
            throw new IllegalStateException("棋盘缓存不能为空！！");
        paint.setAlpha(255);
        canvas.drawBitmap(boardCache, 0, 0, paint);
        LogUtils.e("从缓存中绘制棋盘");
    }

    /**
     * 绘制棋子
     *
     * @param canvas 需要被绘制的画布
     */
    private void drawChess(Canvas canvas) {
        for (int row = 0; row < chessArray.length; row++) {
            for (int column = 0; column < chessArray[row].length; column++) {
                Chess chess = chessArray[row][column];
                //chess，说明棋盘格上空的，跳过
                if (chess == null) continue;
                float[] position = getChessPosition(row, column);
                paint.setColor(chess.getFillColor());
                paint.setStyle(Paint.Style.FILL);
                canvas.drawCircle(position[0], position[1], chessRadius, paint);
                //描边
                paint.setStrokeWidth(1);
                paint.setStyle(Paint.Style.STROKE);
                paint.setColor(chess.getStrokeColor());
                canvas.drawCircle(position[0], position[1], chessRadius, paint);
            }
        }
    }

    /**
     * 绘制触摸效果
     *
     * @param canvas 需要被绘制的画布
     */
    private void drawTouchEffect(Canvas canvas) {
        paint.setColor(0x66000000);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(2);
        if (touchedRowCol != null) {
            float[] position = getChessPosition(touchedRowCol[0], touchedRowCol[1]);
            canvas.drawCircle(position[0], position[1], chessRadius * 2.2f, paint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        getParent().requestDisallowInterceptTouchEvent(true);

        if (winner != null)
            return true;

        //人机模式 & 机器人正在思考
        if (isRobotMode && robot.isThinking())
            return true;

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                //更新当前用户 点击的行、列位置，并更新绘制
                touchedRowCol = getChessRowAndColumn(event.getX(), event.getY());
                invalidate();
                break;
            case MotionEvent.ACTION_CANCEL:
                //移除当前用户 点击的行、列位置，并更新绘制
                touchedRowCol = null;
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                if (putChessOnBoard()) {
                    //播放音效
                    soundEngine.playSoundEffect();
                    //落棋成功后如果是 机器人模式，则机器人下棋
                    if (isRobotMode)
                        robot.think();
                }
                break;
        }
        return true;
    }

    /**
     * 把当前的棋子放置在棋盘上 {@link ChessBoardView#touchedRowCol}
     *
     * @return true 为放置成功
     */
    private boolean putChessOnBoard() {
        //标记是否落棋成功（防止在同一个地方放置）
        boolean putDown = false;
        //若用户的点击的位置不为空，则认为已落下棋子
        if (touchedRowCol != null) {
            //新棋子
            Chess newChess = null;

            Chess chess = chessArray[touchedRowCol[0]][touchedRowCol[1]];
            //为 null 说明这个位置还没放置过棋子
            if (chess == null) {
                //新棋的类型
                Chess.Type newType = Chess.reverseType(lastChessType);
                if (newType == Chess.Type.BLACK) {
                    newChess = new BlackChess(touchedRowCol[0], touchedRowCol[1]);
                } else {
                    newChess = new WhiteChess(touchedRowCol[0], touchedRowCol[1]);
                }
                lastChessType = newType;

                //放置新棋子
                chessArray[touchedRowCol[0]][touchedRowCol[1]] = newChess;

                putDown = true;
            }

            //移除用户触摸位置、更新视图
            touchedRowCol = null;
            invalidate();

            if (newChess != null) {
                //放到历史记录中
                historySteps.add(newChess);
                //检查是否有人获胜
                checkWin(newChess);
            }

        }

        return putDown;
    }

    /**
     * 检查当前棋子落下后，是否有人获胜
     *
     * @param chess 当前落下的棋子
     */
    private void checkWin(Chess chess) {
        Chess.Type chessType = chess.getChessType();
        if (checkWinAtHorizontal(chess)) {
            someOneWin(chessType);
        } else if (checkWinAtVertical(chess)) {
            someOneWin(chessType);
        } else if (checkWinAtBottomLeftToTopRight(chess)) {
            someOneWin(chessType);
        } else if (checkWinAtTopLeftToRightBottom(chess)) {
            someOneWin(chessType);
        }
    }

    /**
     * 检查是否在水平方向上获得胜利
     *
     * @param chess 当前落下的棋子
     * @return true 为获得胜利
     */
    private boolean checkWinAtHorizontal(Chess chess) {
        //同色棋子数量, 默认1 为自身
        int count = 1;
        //向左, 行不变，列递减, 一直到第0列 (默认-1 排除自身)
        for (int column = chess.column - 1; column >= 0; column--) {
            if (isEmptyOrPlacedOtherTypeChess(chess, chess.row, column)) break;
            count += 1;
        }
        //向右, 行不变，列递增, 一直到最后一列 (默认+1 排除自身)
        for (int column = chess.column + 1; column < COLUMN_COUNT; column++) {
            if (isEmptyOrPlacedOtherTypeChess(chess, chess.row, column)) break;
            count += 1;
        }
        return count >= 5;
    }

    /**
     * 检查是否在垂直方向上获得胜利
     *
     * @param chess 当前落下的棋子
     * @return true 为获得胜利
     */
    private boolean checkWinAtVertical(Chess chess) {
        //同色棋子数量, 默认1 为自身
        int count = 1;
        //向上, 列不变, 行递减, 一直到第0行 (默认-1 排除自身)
        for (int row = chess.row - 1; row >= 0; row--) {
            if (isEmptyOrPlacedOtherTypeChess(chess, row, chess.column)) break;
            count += 1;
        }
        //向下, 列不变, 行递增, 一直到最后一行 (默认+1 排除自身)
        for (int row = chess.row + 1; row < ROW_COUNT; row++) {
            if (isEmptyOrPlacedOtherTypeChess(chess, row, chess.column)) break;
            count += 1;
        }
        return count >= 5;
    }

    /**
     * 检查是否在 左下到右上 方向上获得胜利
     *
     * @param chess 当前落下的棋子
     * @return true 为获得胜利
     */
    private boolean checkWinAtBottomLeftToTopRight(Chess chess) {
        //同色棋子数量, 默认1 为自身
        int count = 1;
        //向左下方 行递增, 列递减, 一直到最后一行 和 第0列 (默认行+1、列-1 排除自身)
        for (int row = chess.row + 1, column = chess.column - 1; row < ROW_COUNT && column >= 0; row++, column--) {
            if (isEmptyOrPlacedOtherTypeChess(chess, row, column)) break;
            count += 1;
        }
        //向右上方 行递减, 列递加, 一直到第0行 和 最后一列 (默认行-1、列+1 排除自身)
        for (int row = chess.row - 1, column = chess.column + 1; row >= 0 && column < COLUMN_COUNT; row--, column++) {
            if (isEmptyOrPlacedOtherTypeChess(chess, row, column)) break;
            count += 1;
        }
        return count >= 5;
    }

    /**
     * 检查是否在 左上到右下 方向上获得胜利
     *
     * @param chess 当前落下的棋子
     * @return true 为获得胜利
     */
    private boolean checkWinAtTopLeftToRightBottom(Chess chess) {
        //同色棋子数量, 默认1 为自身
        int count = 1;
        //向左上方 行递减, 列递减, 一直到第0行 和 第0列 (默认行-1、列-1 排除自身)
        for (int row = chess.row - 1, column = chess.column - 1; row >= 0 && column >= 0; row--, column--) {
            if (isEmptyOrPlacedOtherTypeChess(chess, row, column)) break;
            count += 1;
        }
        //向右下方 行递增, 列递增, 一直到第最后一行 和 最后一列 (默认行+1、列+1 排除自身)
        for (int row = chess.row + 1, column = chess.column + 1; row < ROW_COUNT && column < COLUMN_COUNT; row++, column++) {
            if (isEmptyOrPlacedOtherTypeChess(chess, row, column)) break;
            count += 1;
        }
        return count >= 5;
    }

    /**
     * 判断是否为空，或者放置了其他类型的棋子
     *
     * @param chess  当前棋子
     * @param row    要比较棋子的行
     * @param column 要比较棋子的列
     * @return true 为空 或者 放置了其他类型的棋子
     */
    private boolean isEmptyOrPlacedOtherTypeChess(Chess chess, int row, int column) {
        Chess curChess = chessArray[row][column];
        //没有棋子 或 不是一类
        return curChess == null || !chess.isSameType(curChess);
    }

    /**
     * 有人赢了
     *
     * @param type 赢的一方棋子类型
     */
    private void someOneWin(Chess.Type type) {
        winner = type;
        if (winListener != null)
            winListener.onNewWin(type);
    }


    /**
     * 根据给的 x/y位置，获取棋子的行、列位置
     *
     * @param x x
     * @param y y
     * @return 行、列
     */
    private int[] getChessRowAndColumn(float x, float y) {
        int[] rowCol = new int[2];
        //减去偏移 除以间距 算出行、列位置
        int row = Math.round((y - offsetTop) / gridVerticalSpacing);
        int col = Math.round((x - offsetLeft) / gridHorizontalSpacing);
        //防止 行/列 超出棋盘边界
        rowCol[0] = row < 0 ? 0 : Math.min(row, ROW_COUNT - 1);
        rowCol[1] = col < 0 ? 0 : Math.min(col, COLUMN_COUNT - 1);
        return rowCol;
    }

    /**
     * 根据行、列位置 获取棋子 圆心的位置
     *
     * @param row    棋子所在的行
     * @param column 棋子所在的列
     * @return 棋子圆心的位置
     */
    private float[] getChessPosition(int row, int column) {
        float[] pos = new float[2];
        //加上偏移位置
        pos[0] = offsetLeft + column * gridHorizontalSpacing;
        pos[1] = offsetTop + row * gridVerticalSpacing;
        return pos;
    }

    /**
     * 判断是否为空
     *
     * @param row    要比较棋子的行
     * @param column 要比较棋子的列
     * @return true 为空
     */
    private boolean isEmptyOnBoard(int row, int column) {
        Chess curChess = chessArray[row][column];
        //没有棋子 或 不是一类
        return curChess == null;
    }

    /**
     * 重置棋盘
     */
    public void resetChessboard() {
        winner = null;
        lastChessType = Chess.Type.WHITE;
        if (!historySteps.isEmpty()) {
            for (Chess[] chess : chessArray)
                Arrays.fill(chess, null);
        }
        historySteps.clear();
        invalidate();
        LogUtils.e("重置棋盘");
    }

    /**
     * 悔棋
     */
    public void regret() {
        if (winner != null)
            return;

        if (historySteps.isEmpty()) {
            Toast.makeText(getContext(), "无棋可悔", Toast.LENGTH_SHORT).show();
            return;
        }

        for (int i = isRobotMode ? 2 : 1; i > 0; i--) {
            Chess chess = historySteps.remove(historySteps.size() - 1);
            chessArray[chess.row][chess.column] = null;
            lastChessType = Chess.reverseType(chess.getChessType());

            LogUtils.e(String.format("%s 悔棋:(row:%s, column:%s)",
                    chess.getChessType().name(), chess.row, chess.column));
        }
        invalidate();
    }

    public void setWinListener(WinListener winListener) {
        this.winListener = winListener;
    }

    /**
     * 重新读取配置
     */
    public void reloadSetting() {
        isRobotMode = SettingSp.isRobotMode();
        resetChessboard();
    }

    public void release() {
        soundEngine.release();
    }

    /**
     * 机器人
     */
    private class Robot {

        private boolean isThinking;

        public void think() {
            //如果赢家已产生，则停止
            if (winner != null)
                return;
            isThinking = true;
            thinkInternal();
        }

        public boolean isThinking() {
            return isThinking;
        }

        private void thinkInternal() {
            new Thread(() -> {

                //如果拦截成功，落棋
                if (intercept()) {
                    thinkComplete();
                    return;
                }

                //拦截不成功，随机在一个地方落棋
                Chess tempChess;
                do {
                    int randomRow = (int) (ROW_COUNT * Math.random());
                    int randomCol = (int) (COLUMN_COUNT * Math.random());
                    tempChess = chessArray[randomRow][randomCol];
                    if (tempChess == null) {
                        touchedRowCol = new int[]{randomRow, randomCol};
                        break;
                    }
                } while (true);

                thinkComplete();
            }).start();
        }

        private void thinkComplete() {
            postDelayed(() -> {
                putChessOnBoard();
                isThinking = false;
            }, 150);
        }

        /**
         * 尝试拦截玩家的棋
         *
         * @return true 为拦截成功
         */
        boolean intercept() {
            if (historySteps.size() != 0) {
                //最后一次
                Chess chess = historySteps.get(historySteps.size() - 1);
                Map<Integer, Integer> directionMap = new HashMap<>();
                for (; ; ) {
                    int ways = 0;
                    for (Integer key : directionMap.keySet()) {
                        if (directionMap.get(key) != null) {
                            ways++;
                        }
                    }
                    //所有方法都试过了，依然找不到拦截位置
                    if (ways == 8)
                        return false;
                    //拦截方向：0 上, 1 下, 2 左, 3 右, 4 坐下, 5 右上, 6 左上, 7 右下
                    int direction = (int) (Math.random() * 8);
                    //当前方法试过了，跳过此次
                    if (directionMap.get(direction) != null)
                        continue;

                    switch (direction) {
                        case 0:
                            if (lookAbove(chess)) return true;
                            break;
                        case 1:
                            if (lookDown(chess)) return true;
                            break;
                        case 2:
                            if (lookLeft(chess)) return true;
                            break;
                        case 3:
                            if (lookRight(chess)) return true;
                            break;
                        case 4:
                            if (lookLeftBottom(chess)) return true;
                            break;
                        case 5:
                            if (lookRightTop(chess)) return true;
                            break;
                        case 6:
                            if (lookLeftTop(chess)) return true;
                            break;
                        case 7:
                            if (lookRightBottom(chess)) return true;
                            break;
                    }
                    //标记当前方法已经尝试过了
                    directionMap.put(direction, direction);
                }


            }
            return false;
        }

        private boolean lookAbove(Chess chess) {
            for (int row = chess.row - 1; row >= 0; row--) {
                if (!isEmptyOnBoard(row, chess.column)) break;
                touchedRowCol = new int[]{row, chess.column};
                return true;
            }
            return false;
        }

        private boolean lookDown(Chess chess) {
            for (int row = chess.row + 1; row < ROW_COUNT; row++) {
                if (!isEmptyOnBoard(row, chess.column)) break;
                touchedRowCol = new int[]{row, chess.column};
                return true;
            }
            return false;
        }

        private boolean lookLeft(Chess chess) {
            for (int column = chess.column - 1; column >= 0; column--) {
                if (!isEmptyOnBoard(chess.row, column)) break;
                touchedRowCol = new int[]{chess.row, column};
                return true;
            }
            return false;
        }

        private boolean lookRight(Chess chess) {
            for (int column = chess.column + 1; column < COLUMN_COUNT; column++) {
                if (!isEmptyOnBoard(chess.row, column)) break;
                touchedRowCol = new int[]{chess.row, column};
                return true;
            }
            return false;
        }


        private boolean lookLeftBottom(Chess chess) {
            for (int row = chess.row + 1, column = chess.column - 1; row < ROW_COUNT && column >= 0; row++, column--) {
                if (!isEmptyOnBoard(row, column)) break;
                touchedRowCol = new int[]{row, column};
                return true;
            }
            return false;
        }

        private boolean lookRightTop(Chess chess) {
            for (int row = chess.row - 1, column = chess.column + 1; row >= 0 && column < COLUMN_COUNT; row--, column++) {
                if (!isEmptyOnBoard(row, column)) break;
                touchedRowCol = new int[]{row, column};
                return true;
            }
            return false;
        }

        private boolean lookLeftTop(Chess chess) {
            for (int row = chess.row - 1, column = chess.column - 1; row >= 0 && column >= 0; row--, column--) {
                if (!isEmptyOnBoard(row, column)) break;
                touchedRowCol = new int[]{row, column};
                return true;
            }
            return false;
        }

        private boolean lookRightBottom(Chess chess) {
            for (int row = chess.row + 1, column = chess.column + 1; row < ROW_COUNT && column < COLUMN_COUNT; row++, column++) {
                if (!isEmptyOnBoard(row, column)) break;
                touchedRowCol = new int[]{row, column};
                return true;
            }
            return false;
        }


    }
}
